package wa;

import java.io.*;
import java.util.*;

import core.SongRecord;

public class WADataParser {
	
	// codes for data field types
	public static final int STR = 1,
						DATE = 2,
						BOOL = 4,
						NUM = 6;
	
	// address at which the file descriptors begin
	public static final long START_ADDR = 0x0200,
	// address at which the file data begin
						DATA_START_ADDR = 0x6200,
	// length of a full field descriptor
						FD_LEN = 768,
	// length of each part of the descriptor
						FD_NAME_LEN = 164,
						FD_DESCR_LEN = 322,
						FD_QUAL_NAME_LEN = 101,
						FD_PATH_LEN = 181,
	// length of a full data item (record)
						DATA_ITEM_LEN = 1032;
	// length of lyrics file block
	public static final long LYRICS_BLOCK_LEN = 512,
						LYRICS_HEAD_LEN = 18;
	// number of data fields
	public static final int NUM_FIELDS = 32;
	public static final String INDEX_FILE = "WASongs.DAT",
						LYRICS_FILE = "WASongs.BLB";
	
	/*
	public static void parseFieldDescriptors (String inFilename, String outFilename) {
		WAFieldDescriptor[] fds = new WAFieldDescriptor[NUM_FIELDS];
		
		try {
			RandomAccessFile file = new RandomAccessFile(inFilename, "r");
			
			// read field descriptors
			for (int i=0; i<fds.length; i++) {
				long offset = START_ADDR + ((long)i * FD_LEN);
				short id, type, length;
				String name;
				
				// read name
				file.seek(offset);
				id = Short.reverseBytes(file.readShort());
				name = readString(file);
				
				// read description 
				offset += FD_NAME_LEN;
				file.seek(offset);
				type = Short.reverseBytes(file.readShort());
				// have read 2 bytes; now discard next 3 bytes
				offset += 5;
				file.seek(offset);
				length = Short.reverseBytes(file.readShort());
				fds[i] = new WAFieldDescriptor((int)id, name, (int)type, (int)length);
			}
			
			// write out
			PrintWriter outFile = new PrintWriter(new File(outFilename));
			System.out.printf("%-4s %-16s %-4s %-6s\n", 
					"ID", "Description", "Type", "Length");
			for (int i=0; i<fds.length; i++) {
				System.out.printf("%4d %-16s %-4d %-6d\n",
						fds[i].id, fds[i].name, fds[i].type, fds[i].length);
				outFile.printf("%d,%s,%d,%d\r\n", 
						fds[i].id, fds[i].name, fds[i].type, fds[i].length);
			}
			outFile.close();
		}
		catch (FileNotFoundException ex) {
			ex.printStackTrace();
		}
		catch (IOException ex) {
			ex.printStackTrace();
		}
	}
	*/
	/**
		Parses WASongs.DAT data file into a csv file containing the data.
	*/
	public static void parseSongFile (String inFilename, String outFilename) {
		
		WAFieldDescriptor[] fds = null;
		WADataItem[] dataItems = null;
		
		try {
			RandomAccessFile file = new RandomAccessFile(inFilename, "r");
			
			fds = readFieldDescriptors(file);
			dataItems = readDataItems(file, fds);
			
			// open the output file
			PrintWriter outFile = new PrintWriter(new PrintStream((new File(outFilename))), true);
			
			// write header
			for (int i=0; i<fds.length; i++) {
				outFile.print(fds[i].name);
				if (i < fds.length - 1)
					outFile.print(",");
				else
					outFile.println();
			}
			
			// write data
			for(WADataItem item : dataItems) {
				for (int i=0; i<item.fields.length; i++) {
					if (item.fields[i] != null) {
						String str = item.fields[i].toString();
						// surround with quotes if data contains commas
						if (str.contains(","))
							str = "\"" + str + "\"";
						
						outFile.print(str);
					}

					// print delimiter
					if (i < (item.fields.length - 1))
						outFile.print(",");
					else
						outFile.println();
				}
			}
			outFile.close();
		}
		catch (FileNotFoundException ex) {
			ex.printStackTrace();
		}
		catch (IOException ex) {
			ex.printStackTrace();
		}
	}
	
	/**
	 * Deprecated. Use importWADatabase() in SongIndex.
	 * @param waDir
	 * @param dataDir
	 */
	@Deprecated
	public static void importWAData(String waDir, String dataDir) {
		File inDir = new File(waDir);
		File outDir = new File(dataDir);
		if (!inDir.isDirectory() || !outDir.isDirectory())
			return;
		
		try {
			RandomAccessFile indexFile = new RandomAccessFile(waDir+INDEX_FILE, "r");
			RandomAccessFile lyricsFile = new RandomAccessFile(waDir+LYRICS_FILE, "r");
			
			WAFieldDescriptor[] fds = readFieldDescriptors(indexFile);
			WADataItem[] dataItems = readDataItems(indexFile, fds);
			
			lyricsFile.seek(LYRICS_BLOCK_LEN);	// skip past header (512 byte block)
			
			for(int i=0; i<dataItems.length; i++) {		// for each song in index
				
				Object lyrics2Field = dataItems[i].fields[31];	// get LYRICS2 field
				if (lyrics2Field == null)	// if lyrics don't exist
					continue;
				
				// ACTION: add the song to the song index file
				
				int lyricsNo = ((Integer)lyrics2Field).intValue();
				StringBuilder sb = new StringBuilder();
				
				// read lyrics
				WALyricsBlock wlb = null;			// keep reading blocks until
				do {								// lyrics number matches or exceeds target
					wlb = readLyricsBlock(lyricsFile);
					if (wlb.id == lyricsNo)
						sb.append(wlb.text);
					
				} while (wlb.id <= lyricsNo);
				
				// write out the data
				if (sb.length() > 0) {
					int id = ((Integer)dataItems[i].fields[0]).intValue();
					SongRecord sr = new SongRecord(id, (String)dataItems[i].fields[2], dataDir);
					sr.setField(SongRecord.AUTHOR, (String)dataItems[i].fields[3]);
					sr.setField(SongRecord.COPYRIGHT, (String)dataItems[i].fields[4]);
					sr.setLyrics(sb.toString().trim());
				}
			}
		}
		catch (FileNotFoundException ex) {
			ex.printStackTrace();
		}
		catch (IOException ex) {
			ex.printStackTrace();
		}
	}
	
	/*	DEBUG FUNCTION
	private static void readLyrics(RandomAccessFile f) {
		try {
			f.seek(0L);
			System.out.printf("file code: 0x%x\n", Integer.reverseBytes(f.readInt()));
			f.seek(LYRICS_BLOCK_LEN);
			
			WALyricsBlock wlb = readLyricsBlock(f);
			System.out.println("song lyrics no: " + wlb.id);
			System.out.println("lyrics:");
			System.out.println(wlb.text);
		}
		catch (IOException e){ 
			e.printStackTrace();
		}
	}
	*/
	
	public static WAFieldDescriptor[] readFieldDescriptors(RandomAccessFile file)
			throws IOException {
		
		WAFieldDescriptor[] fds = new WAFieldDescriptor[NUM_FIELDS];
		for (int i=0; i<fds.length; i++) {
			long offset = START_ADDR + ((long)i * FD_LEN);
			short id, type, length;
			String name;
			
			// read name
			file.seek(offset);
			id = Short.reverseBytes(file.readShort());
			name = readString(file);
			
			// read description 
			offset += FD_NAME_LEN;
			file.seek(offset);
			type = Short.reverseBytes(file.readShort());
			// have read 2 bytes; now discard next 3 bytes
			offset += 5;
			file.seek(offset);
			length = Short.reverseBytes(file.readShort());
			fds[i] = new WAFieldDescriptor((int)id, name, (int)type, (int)length);
		}
		return fds;
	}
	
	public static WADataItem[] readDataItems(RandomAccessFile file, WAFieldDescriptor[] fds) 
			throws IOException {
		
		ArrayList<WADataItem> dataItems = new ArrayList<WADataItem>();
		
		long offset = DATA_START_ADDR;
		try {
			while (file.getFilePointer() < file.length()) {
				WADataItem wdi = new WADataItem();
				file.seek(offset + 25);		// skip the first 25 bytes of crap
				for (int i=0; i<NUM_FIELDS; i++)
					wdi.fields[i] = readFieldItem(file, fds[i]);
				dataItems.add(wdi);
				offset += DATA_ITEM_LEN;	// seek to the next data item (record)
				file.seek(offset);
			}
		}
		catch (EOFException ex) {}
		
		return dataItems.toArray(new WADataItem[dataItems.size()]);
	}
	
	/**
	 * Reads a 512-byte lyrics block into a WALyricsBlock object.
	 * @param f
	 * @return	the lyrics block, or null if EOF reached
	 * @throws IOException
	 */
	public static WALyricsBlock readLyricsBlock(RandomAccessFile f) 
			throws IOException {
		
		try {
			//System.out.printf("start read at: 0x%x\n", f.getFilePointer());
			@SuppressWarnings("unused")
			int blockNo = Integer.reverseBytes(f.readInt());
			//System.out.println("block no: " + blockNo);
			f.skipBytes(6);
			//System.out.printf("read song lyric no at address: 0x%x\n", f.getFilePointer());
			int lyricNo = Integer.reverseBytes(f.readInt());
			f.skipBytes(4);
			
			final int dataLength = (int)(LYRICS_BLOCK_LEN - LYRICS_HEAD_LEN);
			StringBuilder sb = new StringBuilder(dataLength);
			
			for (int i=0; i<dataLength; i++) {
				char c = (char)f.readUnsignedByte();
				// replace unusual characters
				//   0x91 : left single quote
				//   0x92 : right single quote
				if (c == 0x91) c = 0x2018;
				else if (c == 0x92) c = 0x2019;
				
				sb.append(c);
			}
			
			return new WALyricsBlock(lyricNo, sb.toString());
		} catch (EOFException eof) {
			return null;
		}
	}
	
	public static Object readFieldItem(RandomAccessFile f, WAFieldDescriptor fd)
			throws IOException {
		
		Object retVal = null;
		boolean valueExists = f.readBoolean();
		
		if (valueExists) {
			switch (fd.type) {
				case 1:	 {	// string
					int strlen = fd.length;
					StringBuilder sb = new StringBuilder(strlen);
					while (strlen > 0) {
						sb.append((char)f.readUnsignedByte());
						strlen--;
					}
					retVal = sb.toString().trim();
					break;
				}
				case 2:		// 4-byte date (?)
				case 6:	 {	// number
					int i = Integer.reverseBytes(f.readInt());
					retVal = new Integer(i);
					break;
				}
				case 4:	{	// boolean
					short s = f.readShort();
					if (s == 0)
						retVal = new Boolean(false);
					else
						retVal = new Boolean(true);
					break;
				}
				case 11: {	// 8-byte datetime (?)
					long l = Long.reverseBytes(f.readLong());
					retVal = new Long(l);
					break;
				}
				case 5635: {	// pointer to data in other file (?)
					f.readInt();	// discard first byte
					int k = Integer.reverseBytes(f.readInt());
					retVal = new Integer(k);
					break;
				}
			}
		}
		// otherwise, skip ahead the length of the field and return null
		else {
			f.skipBytes(fd.length);
			retVal = null;
		}
		
		return retVal;
	}
	
	private static String readString(RandomAccessFile f) 
			throws IOException {
			
		byte strlen = f.readByte();
		StringBuilder sb = new StringBuilder(strlen);
		while (strlen > 0) {
			sb.append((char)f.readUnsignedByte());
			strlen--;
		}
		
		return sb.toString().trim();
	}
	
	public static void main(String[] args) {
	/*
		if (args.length == 2)
			//parseSongFile(args[0], args[1]);
			importWAData(args[0], args[1]);
		else
			//System.out.println("Usage:\n WADataParser [input filename] [output filename]");
			System.out.println("Usage:\n WADataParser [WA directory] [output directory]");
	*/
//		parseSongFile("data/Mark WA DB/WASongs.DAT", "data/markIndex.csv");
		parseSongFile("data/Nat/WASongs.DAT", "data/natIndex.csv");
		parseSongFile("data/Natalie/WASongs.DAT", "data/natalieIndex.csv");
		parseSongFile("data/Nat's WA/WASongs.DAT", "data/natWAIndex.csv");
		parseSongFile("data/wa/WASongs.DAT", "data/nateIndex.csv");
	}
}
